package ex1;

import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import ex1.ga.Chromosome;
import ex1.ga.FitnessFunction;
import ex1.ga.GeneticAlgorithm;
import ex1.game.HistoryLookupPlayer;
import ex1.game.Player;
import ex1.game.PrisonersDilemmaGame;
import ex1.game.players.*;
import ex1.utils.Utils;

public class Main {

	public static int HISTORY_LEVEL = 2;
	public static final int NUM_OF_CHROMOSOMS_FROM_EACH_OPPONENT = 5;
	public static final int NUM_OF_GAMES_MENTOR = 1;
	public static final int NUM_OF_GAMES_COEVOLUTION = 1;

	public static final int POPULATION_SIZE = 300;
	public static final int NUM_OF_ROUNDS = 200;
	public static final int ELITISM_FIRST_PHASE = 8;
	public static final int ELITISM_SECOND_PHASE = 2;
	public static final double CROSSOVER_PROB = 0.7;
	public static final double MUTATION_PROB = 0.01;

	/**
	 * @param args
	 */
	public static void main(String[] args) 
	{
		long seed = 1304177144863L; //System.currentTimeMillis();
				
		Utils.setSeed(seed);
		
		System.out.println("Seed: " + seed);
		
		for (HISTORY_LEVEL = 2; HISTORY_LEVEL <= 4; HISTORY_LEVEL++)
		{
			String bestChromosome = new Main().findBestChromosome().toString();
			String fileName = "200165769_065968083_H" + HISTORY_LEVEL + ".txt";
			
			try 
			{
				FileWriter fw = new FileWriter(fileName);
				fw.write(bestChromosome.toString());
				fw.close();
			} catch (IOException e) 
			{
				System.out.println("Error writing to output file!");
				return;
			}
			
		}		
	}

	public Chromosome findBestChromosome() {
		
		List<Player> opponents = new ArrayList<Player>();
		opponents.add(new PavlovPlayer());
		opponents.add(new TitForTatPlayer());
		opponents.add(new GrimPlayer());
		opponents.add(new NaivePlayer());
		opponents.add(new NaiveProberPlayer());
		opponents.add(new TruePeaceMaker());
		opponents.add(new TitForTwoTatsPlayer());
		opponents.add(new RandomPlayer());
		

		int chromosomeSize = ((int) Math.pow(4, HISTORY_LEVEL))
				+ (2 * HISTORY_LEVEL);

		List<Chromosome> winners = new ArrayList<Chromosome>();
		for (Player player : opponents) {
			
			// Add X best Chromosomes to the list which best against specific
			// opponent (Mentor).
			GeneticAlgorithm ga = new GeneticAlgorithm(POPULATION_SIZE,
					chromosomeSize, ELITISM_FIRST_PHASE, CROSSOVER_PROB,
					MUTATION_PROB);

			SpecificStrategyFitness fitnessFunc = new SpecificStrategyFitness(
					player);

			System.out.println("Opponent,Round,BestChromosome,BestFitness,AverageFitness");
			for (int i = 0; i < NUM_OF_ROUNDS; i++) {
				
				System.out.print(player.toString() + "," + i + ",");
				//System.out.println("\tRound " + i);
				ga.runOneRound(fitnessFunc);
			}

			for (int i = 0; i < NUM_OF_CHROMOSOMS_FROM_EACH_OPPONENT; i++) {
				winners.add(ga.getPopulation().get(ga.getPopulation().size() - i - 1));
				System.out.println("Winner " + i  + ": " + ga.getPopulation().get(ga.getPopulation().size() - i - 1));
			}
		}


		// Find best chromosome using co-evolution
		GeneticAlgorithm lastGA = new GeneticAlgorithm(winners, chromosomeSize,
				ELITISM_SECOND_PHASE, CROSSOVER_PROB, MUTATION_PROB);

		System.out.println("Starting Co-Evolution : ");
		
		CoevolutionFitness coevolFitnessFunc = new CoevolutionFitness(lastGA);
		for (int i = 0; i < NUM_OF_ROUNDS; i++) {
			//System.out.println("\tRound " + i);
			lastGA.runOneRound(coevolFitnessFunc);
		}

		Chromosome theWinner = lastGA.getPopulation().get(lastGA.getPopulation().size() - 1);
		System.out.println("The winner is: " + theWinner);

		return theWinner;
	}

	private class SpecificStrategyFitness implements FitnessFunction {
		private Player _opponent;

		public SpecificStrategyFitness(Player opponent) {
			_opponent = opponent;
		}

		@Override
		public int getFitness(Chromosome chromosome) {

			int totalFitness = 0;
			//System.out.println("-- Playing " + chromosome);
			
			for (int i = 0; i < NUM_OF_GAMES_MENTOR; i++) {

				Player you = new HistoryLookupPlayer(HISTORY_LEVEL, chromosome);

				PrisonersDilemmaGame game = new PrisonersDilemmaGame(you,
						_opponent);
				totalFitness += game.runGame();

			}

			return totalFitness;
		}

		@Override
		public boolean fitnessCanChange() {
			return true;
		}

	}

	private class CoevolutionFitness implements FitnessFunction {
		private GeneticAlgorithm _ga;

		public CoevolutionFitness(GeneticAlgorithm ga) {
			_ga = ga;
		}

		@Override
		public int getFitness(Chromosome chromosome) {

			int totalFitness = 0;
			for (Chromosome opponentChrom : _ga.getPopulation()) {

				for (int i = 0; i < NUM_OF_GAMES_COEVOLUTION; i++) {

					Player you = new HistoryLookupPlayer(HISTORY_LEVEL,
							chromosome);
					Player opponent = new HistoryLookupPlayer(HISTORY_LEVEL,
							opponentChrom);

					PrisonersDilemmaGame game = new PrisonersDilemmaGame(you,
							opponent);
					totalFitness += game.runGame();
				}
			}

			return totalFitness;
		}

		@Override
		public boolean fitnessCanChange() {
			return true;
		}

	}
}
